import typing
import logging
import os.path
import copy
from collections import defaultdict
from itertools import chain

# https://github.com/uiri/toml/issues/252
import pytoml as toml

from tinyops.library.json_helper import JsonHelper
from tinyops.library.config import (
    TaskConfig,
    ServerConfig,
    DockerConfig,
    WebhookConfig,
    WebuiConfig,
    GroupNotifierConfig,
    SmtpNotifierConfig,
    ServerChanNotifierConfig,
    NotifierConfig,
    TimerConfig,
    AlerterConfig,
)


from .config import process_config, resolve_notifiers


LOG = logging.getLogger(__name__)


NotifierConfigTyping = typing.Union[
    GroupNotifierConfig,
    SmtpNotifierConfig,
    ServerChanNotifierConfig,
]


_SECRET_KEYS = {'password', 'secret', 'sckey', 'token', 'private_key'}


def _secure_config(value):
    if isinstance(value, dict):
        ret = {}
        for k, v in value.items():
            if v and k in _SECRET_KEYS:
                v = '******'
            ret[k] = _secure_config(v)
        return ret
    elif isinstance(value, list):
        return [_secure_config(x) for x in value]
    else:
        return value


class Config:

    def __init__(self, config: dict):
        self._raw = config
        self._config = config
        self.debug = config['debug']
        self.timezone = config['timezone']
        self.timezone_name = config['timezone_name']
        self.webui = WebuiConfig(config['webui'])
        self._servers = defaultdict(list)
        for x in config['servers']:
            x = ServerConfig(x)
            self._servers[x.name].append(x)
            for label in (x.label or []):
                self._servers[label].append(x)
        self._dockers = defaultdict(list)
        for x in config['dockers']:
            x = DockerConfig(x)
            self._dockers[x.name].append(x)
            for label in (x.label or []):
                self._dockers[label].append(x)
        self._notifiers = {x['name']: NotifierConfig(x) for x in config['notifiers']}
        self._tasks = {x['name']: TaskConfig(x) for x in config['tasks']}
        self._webhooks = {x['name']: WebhookConfig(x) for x in config['webhooks']}
        self._timers = {x['name']: TimerConfig(x) for x in config['timers']}

    @property
    def raw(self):
        return self._raw

    @property
    def raw_secure(self):
        return _secure_config(copy.deepcopy(self._raw))

    @property
    def directory(self):
        return self._config['directory']

    @property
    def timers(self) -> typing.Iterable[TimerConfig]:
        return list(self._timers.values())

    @property
    def tasks(self) -> typing.Iterable[TaskConfig]:
        return list(self._tasks.values())

    @property
    def servers(self) -> typing.Iterable[ServerConfig]:
        return list(chain(*self._servers.values()))

    @property
    def dockers(self) -> typing.Iterable[DockerConfig]:
        return list(chain(*self._dockers.values()))

    @property
    def webhooks(self) -> typing.Iterable[WebhookConfig]:
        return list(self._webhooks.values())

    @property
    def alerter(self) -> AlerterConfig:
        alerter_config = self._config.get('alerter')
        return AlerterConfig(alerter_config) if alerter_config else None

    def server(self, *labels) -> typing.List[ServerConfig]:
        ret = {}
        for label in labels:
            for x in self._servers[label]:
                ret[x.name] = x
        return list(ret.values())

    def docker(self, *labels) -> typing.List[DockerConfig]:
        ret = {}
        for label in labels:
            for x in self._dockers[label]:
                ret[x.name] = x
        return list(ret.values())

    def notifier(self, name: str) -> NotifierConfigTyping:
        try:
            return self._notifiers[name]
        except KeyError:
            raise ValueError(f"notifier {name} not exists") from None

    def notifier_resolved(self, name: str) -> typing.List[NotifierConfigTyping]:
        notifiers = resolve_notifiers(self.raw, {name: [name]})[name]
        return [self.notifier(x) for x in notifiers]

    def task(self, name: str) -> TaskConfig:
        try:
            return self._tasks[name]
        except KeyError:
            raise ValueError(f"task {name} not exists") from None

    def webhook(self, name: str) -> WebhookConfig:
        try:
            return self._webhooks[name]
        except KeyError:
            raise ValueError(f"webhook {name} not exists") from None

    def to_json(self) -> str:
        return JsonHelper.dumps(self._raw, indent=4)

    @classmethod
    def from_json(cls, data: str) -> "Config":
        config = JsonHelper.loads(data)
        return cls(config)

    @classmethod
    def load(cls, *, config=None, content=None, fileobj=None, filepath=None) -> "Config":
        if (not fileobj) and (not content) and (not config):
            if not filepath:
                filepath = os.getenv('TINYOPS_CONFIG') or 'tinyops.toml'
            filepath = os.path.abspath(os.path.expanduser(filepath))
            LOG.info(f'load config {filepath}')
            with open(filepath) as f:
                content = f.read()
        if fileobj:
            content = fileobj.read()
        if content:
            config = toml.loads(content)
        if not config:
            raise ValueError('config info not provided')
        if filepath:
            config['filepath'] = filepath
        config = process_config(config)
        return cls(config)
